package info.liujiachen.test.spring.security.filter;

import info.liujiachen.test.spring.security.util.JwtUtil;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final static String AUTHOR = "Authorization";
    private final static String BEARER = "Bearer ";
    private AuthenticationManager authenticationManager;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    public void afterPropertiesSet() {
        Assert.notNull(this.authenticationManager, "An AuthenticationManager is required");
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        if (Optional.ofNullable(request.getHeader(AUTHOR)).isPresent()) {
            try {
                String token = getToken(request);
                if (Optional.ofNullable(token).isPresent()) {
                    JwtUtil.verify(token);
                    String username = JwtUtil.getUsername(token);
                    this.isCurrentUser(username);
                    String password = JwtUtil.getPassword(token);
                    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
                    Authentication authResult = this.authenticationManager.authenticate(authenticationToken);
                    SecurityContextHolder.getContext().setAuthentication(authResult);
//                    this.refreshToken(response, token);
                    this.onSuccessfulAuthentication(request, response, chain, authResult);
                } else {
                    throw new BadCredentialsException("Token Not Null");
                }
            } catch (AuthenticationException ae) {
                logger.error("处理token错误：", ae);
                SecurityContextHolder.clearContext();
                this.onUnsuccessfulAuthentication(request, response, ae);
            }
        }
    }

    protected String getToken(HttpServletRequest request) {
        String headerAuthor = request.getHeader(AUTHOR);
        String token = null;
        if (Optional.ofNullable(headerAuthor).isPresent()) {
            token = headerAuthor.substring(BEARER.length());
        }
        return token;
    }

    public void isCurrentUser(String username) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (!Optional.ofNullable(auth).isPresent()) {
            throw new UsernameNotFoundException("The current user is not logged in");
        }
        String name = auth.getName();
        if (!(Optional.ofNullable(name).isPresent()
                && name.equals(username))) {
            throw new UsernameNotFoundException("The current user is not logged in");
        }
    }

    protected void refreshToken(HttpServletResponse response, String token) {
        token = JwtUtil.refresh(token);
        response.setHeader(AUTHOR, token);
    }

    protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        chain.doFilter(request, response);
    }

    protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {
        response.sendError(HttpStatus.FORBIDDEN.value());
    }

}
